<?php

namespace think\filesystem\Adapter;

use League\Flysystem\Config;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use OSS\OssClient;

class OssAdapter implements FilesystemAdapter
{

    /**
     * @var OssClient
     */
    protected $ossClient;

    /**
     * 配置参数
     * @var array
     */
    protected $config = [
        "accessId" => "",
        "accessSecret" => "",
        "bucket" => "",
        "endpoint" => "oss-cn-hangzhou.aliyuncs.com",
        "domain" => "",
        "cdn" => "",
    ];

    /**
     * 初始化ossclient
     * @param $config
     */
    public function __construct($config)
    {
        $this->config = array_merge($this->config, $config);
        $this->config['timeout'] = $this->config['timeout'] ?? 3600;
        $this->config['connectTimeout'] = $this->config['connectTimeout'] ?? 10;
        $this->config['isCName'] = $this->config['isCName'] ??false;
        $this->config['token'] = $this->config['token'] ?? null;
    }

    /**
     * 上传文件
     * @param $path
     * @param $contents
     * @param Config $config
     * @return void
     */
    public function write($path, $contents, Config $config): void
    {
        $options = [];
        if ($config->get("Content-Type")) {
            $options["Content-Type"] = $config->get("Content-Type");
        }
        if ($config->get("Content-Md5")) {
            $options["Content-Md5"] = $config->get("Content-Md5");
            $options["checkmd5"] = false;
        }
        $this->getObjClient()->putObject($this->config['bucket'], $path, $contents, $options);
    }

    /**
     * 写入文件流
     * @param $path
     * @param $contents
     * @param Config $config
     * @return void
     */
    public function writeStream($path, $contents, Config $config): void
    {
        $this->write($path, \stream_get_contents($contents), $config);
    }

    /**
     * 移动文件
     * @param string $source
     * @param string $destination
     * @param Config $config
     * @return void
     */
    public function move(string $source, string $destination, Config $config): void
    {
        //拷贝object到指定地方
        $this->copy($source, $destination, $config);
        //删除指定object
        $this->delete($source);
    }

    /**
     * 拷贝文件
     * @param string $source
     * @param string $destination
     * @param Config $config
     * @return void
     */
    public function copy(string $source, string $destination, Config $config): void
    {
        $this->getObjClient()->copyObject($this->config['bucket'], $source, $this->config['bucket'], $destination);
    }

    /**
     * 删除文件
     * @param string $path
     * @return void
     */
    public function delete(string $path): void
    {
        $this->getObjClient()->deleteObject($this->config['bucket'], $path);
    }

    /**
     * 删除目录
     * @param string $path
     * @return void
     */
    public function deleteDirectory(string $path): void
    {
    }

    /**
     * 创建目录
     * @param string $path
     * @param Config $config
     * @return void
     */
    public function createDirectory(string $path, Config $config): void
    {
    }

    /**
     * 文件是否存在
     * @param string $path
     * @return bool
     */
    public function fileExists(string $path): bool
    {
        return $this->getObjClient()->doesObjectExist($this->config['bucket'], $path);
    }

    /**
     * 读取文件
     * @param string $path
     * @return string
     */
    public function read(string $path): string
    {
        return $this->getObjClient()->getObject(
            $this->config['bucket'],
            $path
        );
    }

    /**
     * 读取文件流
     * @param string $path
     * @return resource
     */
    public function readStream(string $path)
    {
        return fopen($this->getUrl($path), 'r');
    }

    /**
     * 获取指定目录下文件列表
     * @param string $path
     * @param bool $deep
     * @return iterable
     */
    public function listContents(string $path, bool $deep): iterable
    {
        $option = [
            'prefix' => $path,
            // 分隔符
            'delimiter' => $deep ? '' : '/',
            // 最大列举个数
            //'max-keys' => 1000,
            // 指定文件名称编码方式为URL。
            'encoding-type' => 'url',
        ];
        $listObjectInfo = $this->getObjClient()->listObjectsV2($this->config['bucket'], $option);
        // 文件列表
        $objectList = $listObjectInfo->getObjectList();
        foreach ($objectList as $file) {
            $lastModified = $file->getLastModified();
            $stats = [
                'Key' => $file->getKey(),
                'Size' => $file->getSize(),
                'LastModified' => empty($lastModified) ? null : strtotime($lastModified),
            ];
            yield $this->normalizeFileInfo($stats);
        }
    }

    /**
     * 获取metadata信息
     * @param string $path
     * @return FileAttributes
     */
    public function mimeType(string $path): FileAttributes
    {
        return $this->getMetadata($path);
    }

    /**
     * 文件meta信息
     * @param string $path
     * @return FileAttributes
     */
    public function fileSize(string $path): FileAttributes
    {
        return $this->getMetadata($path);
    }

    /**
     * 上次修改信息
     * @param string $path
     * @return FileAttributes
     */
    public function lastModified(string $path): FileAttributes
    {
        return $this->getMetadata($path);
    }

    /**
     * 查看文件权限
     * @param string $path
     * @return FileAttributes
     */
    public function visibility(string $path): FileAttributes
    {
        throw new \Exception($path);
    }

    /**
     * 设置读写权限
     * @param string $path
     * @param string $visibility public|private
     * @return void
     */
    public function setVisibility(string $path, string $visibility): void
    {

    }

    /**
     * 格式化url地址
     * @param string $path
     * @return string
     */
    public function getUrl(string $path): string
    {
        return $this->config['cdn'] . "/" . $path;
    }

    /**
     * 获取配置参数
     * @return array
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * 从指定URL抓取资源，并将该资源存储到指定空间中
     * @param string $url
     * @param string $path 指定为"source"时按目标路径存储
     * @return string
     */
    public function fetch(string $url, string $path = 'source')
    {
        ini_set("user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/102.0.0.0 Safari/537.36");
        $contents = file_get_contents($url); //读取文件内容
        $parsedUrl = parse_url($url);
        $filePath = ltrim(pathinfo($parsedUrl['path'], PATHINFO_DIRNAME), '/'); //文件路径
        $fileName = pathinfo($parsedUrl['path'], PATHINFO_BASENAME); //文件名
        //$fileExtension = pathinfo($parsedUrl['path'], PATHINFO_EXTENSION); //文件后缀
        $path = $path == "source" ? $filePath . '/' . $fileName : $path;
        $this->getObjClient()->putObject($this->config['bucket'], $path, $contents);
        return $path;
    }

    /**
     * 获取ossClient
     * @return OssClient
     */
    public function getObjClient()
    {
        if ($this->ossClient) return $this->ossClient;
        $this->ossClient = new OssClient(
            $this->config['accessId'],
            $this->config['accessSecret'],
            $this->config['endpoint'],
            $this->config['isCName'],
            $this->config['token']
        );
        $this->ossClient->setTimeout($this->config['timeout']);
        $this->ossClient->setConnectTimeout($this->config['connectTimeout']);
        return $this->ossClient;
    }

    /**
     * 返回上传签名
     * @param $key
     * @return string
     */
    public function getUploadToken($key)
    {
        return $this->getObjClient()->signUrl($this->config['bucket'], $key);
    }

    /**
     * 文件meta信息
     * @param $path
     * @return FileAttributes
     */
    public function getMetadata($path)
    {
        $meta = $this->getObjClient()->getObjectMeta($this->config['bucket'], $path);
        $stats = [
            'Key' => $path,
            'Size' => $meta['Content-Length'] ?? null,
            'LastModified' => empty($meta['lastmodified']) ? null : strtotime($meta['lastmodified']),
            'Content-Type' => empty($meta['Content-Type']) ?? null,
        ];
        return $this->normalizeFileInfo($stats);
    }

    /**
     * 获取文件信息 转数组：fileAttributes->jsonSerialize()
     * @param array $stats
     * @return FileAttributes
     */
    protected function normalizeFileInfo(array $stats)
    {
        return new FileAttributes(
            $stats['Key'],
            $stats['Size'] ?? null,
            null,
            $stats['LastModified'] ?? null,
            $stats['Content-Type'] ?? null
        );
    }

    /**
     * 生成uploadToken
     * @param $options
     * @return array
     */
    public function getTempKeys($options = [])
    {
        //过期时间设置
        $expire     = $options['expire'] ?? 60;
        $end        = time() + $expire;
        $expiration = str_replace('+00:00', '.000Z', gmdate('c', $end));
        //conditions 设置
        $conditions = $options['conditions'] ?? [['bucket' => $this->config['bucket']]];
        //签名设置
        $arr          = ['expiration' => $expiration, 'conditions' => $conditions];
        $policy       = json_encode($arr);
        $base64Policy = base64_encode($policy);
        $signature    = base64_encode(hash_hmac('sha1', $base64Policy, $this->config['accessId'], true));
        //返回数据
        $data              = [];
        $data['accessid']  = $this->config['accessId'];
        $data['host']      = "http://{$this->config['bucket']}.{$this->config['endpoint']}";
        $data['policy']    = $base64Policy;
        $data['signature'] = $signature;
        $data['expire']    = $end;
        $data['dir']       = $options['dir'] ?? '';  // 这个参数是设置用户上传文件时指定的前缀。
        return $data;
    }

}